﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;

namespace Xiaowei.Services
{
    public class AppServiceManager
    {
        private static readonly Lazy<AppServiceManager> LazyInstance = new Lazy<AppServiceManager>(() =>
        {
            return new AppServiceManager();
        }, LazyThreadSafetyMode.ExecutionAndPublication);

        public static AppServiceManager Instance { get { return LazyInstance.Value; } }

        AppServiceConnection connection;

        private bool appServiceEnable = false;
        private object appServiceEnableLock=new object();
        public bool AppServiceEnable
        {
            get
            {
                return appServiceEnable;
            }
            set
            {
                lock (appServiceEnableLock)
                {
                    if (appServiceEnable != value)
                    {
                        appServiceEnable = value;
                        if (value)
                        {
                            OpenAppService();
                        }
                        else
                        {
                            CloseAppService();
                        }
                    }

                }
            }
        }

        private void OpenAppService()
        {
            _ = ReOpenAppService();
            CreateSendMessageTask();
        }

        private void CloseAppService()
        {
            DisposeConnection();
            while (messagesNeedSend.TryDequeue(out SendMessageTaskAwaitor awaitor))
            {
                awaitor.TaskAwaitor.Set();
            }
            AppServiceConnectedEvent.Set();
            HasMessageEvent.Set();
        }

        private void DisposeConnection()
        {
            AppServiceConnectedEvent.Reset();
            connection?.Dispose();
            connection = null;
        }

        private async Task<bool> ReOpenAppService()
        {
            DisposeConnection();
            var _connection = new AppServiceConnection();
            _connection.AppServiceName = "CommunicationService";
            _connection.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;
            _connection.RequestReceived += Connection_RequestReceived;
            _connection.ServiceClosed += Connection_ServiceClosed;
            Logger.Debug.WriteLine("Connection.Init, Open connection");
            AppServiceConnectionStatus status = await _connection.OpenAsync();
            if (status != AppServiceConnectionStatus.Success)
            {
                Logger.Debug.WriteLine($"Connection.Init, failed!{status}");
                if(appServiceEnable)
                {
                    _ = ReOpenAppService();
                }
                return false;
            }
            else
            {
                Logger.Debug.WriteLine($"Connection.Init, ok!");
                connection = _connection;
                AppServiceConnectedEvent.Set();
                return true;
            }
        }

        private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
            if(appServiceEnable)
            {
                _ = ReOpenAppService();
            }
        }

        private void Connection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            var deferral = args.GetDeferral();
            if (args.Request.Message == null)
            {
                deferral.Complete();
                return;
            }
            if (!FunctionDictionary.ContainsKey(args.Request.Message["Request"].ToString()))
            {
                deferral.Complete();
                return;
            }
            ValueSet result = new ValueSet();
            try
            {
                result = FunctionDictionary[args.Request.Message["Request"].ToString()](args.Request.Message);
            }
            catch (Exception ex)
            {
                Logger.Debug.WriteLine($"AppServiceManager Connection_RequestReceived Exception{ex}");
            }
            _ = args.Request.SendResponseAsync(result);
            deferral.Complete();
            return;
        }

        private object messagesNeedSendLock = new object();

        private Queue<SendMessageTaskAwaitor> messagesNeedSend = new Queue<SendMessageTaskAwaitor>();

        private ManualResetEvent AppServiceConnectedEvent=new ManualResetEvent(false);
        private ManualResetEvent HasMessageEvent = new ManualResetEvent(false);

        public Task<ValueSet> SendMessage(ValueSet msg)
        {
            if (!appServiceEnable) return Task.Run(()=> { return new ValueSet(); });
            lock (messagesNeedSendLock)
            {
                SendMessageTaskAwaitor sendMessageTaskAwaitor = new SendMessageTaskAwaitor();
                ManualResetEvent TaskAwaitor = new ManualResetEvent(false);

                sendMessageTaskAwaitor.TaskAwaitor = TaskAwaitor;
                sendMessageTaskAwaitor.Message = msg;
                messagesNeedSend.Enqueue(sendMessageTaskAwaitor);
                HasMessageEvent.Set();
                return Task.Run(() =>
                {
                    SendMessageTaskAwaitor sendMessageTaskAwaitor2 = sendMessageTaskAwaitor;
                    TaskAwaitor.WaitOne();
                    TaskAwaitor.Dispose();
                    return sendMessageTaskAwaitor2.Result;
                });
            }
        }

        

        private void CreateSendMessageTask()
        {
            Task.Run(
                ()=>
                {
                    while (appServiceEnable)
                    {
                        AppServiceConnectedEvent.WaitOne();
                        HasMessageEvent.WaitOne();
                        lock (messagesNeedSendLock)
                        {
                            while (messagesNeedSend.TryPeek(out SendMessageTaskAwaitor awaitor))
                            {
                                if (connection == null) break;
                                try
                                {
                                    var appServiceAwaitor = HP.UtilsLib.TaskAwaiterHelper.GetTaskAwaiter(
                                        async () =>{ 
                                            return await connection.SendMessageAsync(awaitor.Message);
                                        }
                                        );
                                    appServiceAwaitor.Await();
                                    var response = appServiceAwaitor.GetResult();
                                    appServiceAwaitor.Dispose();
                                    if (response.Status == AppServiceResponseStatus.Success)
                                    {
                                        awaitor.Result = response.Message;
                                        awaitor.TaskAwaitor.Set();
                                    }
                                    messagesNeedSend.Dequeue();

                                }
                                catch (Exception)
                                {
                                    break;
                                }
                            }
                            if(messagesNeedSend.Count==0)
                            {
                                HasMessageEvent.Reset();
                            }                            

                        }

                        
                    }
                }
                );

        }
        class SendMessageTaskAwaitor
        {
            public ValueSet Result { get; set; }
            public ValueSet Message { get; set; }
            public ManualResetEvent TaskAwaitor { get; set; }
        }

        private Dictionary<string, Func<ValueSet, ValueSet>> FunctionDictionary = new Dictionary<string, Func<ValueSet, ValueSet>>();
        public void RegisterFunction(string request, Func<ValueSet, ValueSet> func)
        {
            FunctionDictionary[request] = func;
        }
    }
}
